#pragma once

#include <cassert>
#include <type_traits>
#include <utility>

template<typename MatchType , typename... Types>
class TVariantGetIndex;

template<typename MatchType>
class TVariantGetIndex<MatchType>
{
public:

	static constexpr int GetIndex()
	{
		return -1;  //invalid index
	}
};

template<typename MatchType, typename FirstType, typename... Types>
class TVariantGetIndex<MatchType, FirstType, Types...> : public TVariantGetIndex<MatchType, Types...>
{
};

template<typename MatchType,typename FirstType, typename... Types>
requires std::is_same_v<MatchType, FirstType>
class TVariantGetIndex<MatchType, FirstType, Types...>
{
public:
	static constexpr int GetIndex()
	{
		return sizeof...(Types);
	}
};




template<typename... Types>
class TVariant
{
private:
	void* SingleTypeData = nullptr;
	// index of type, start from last type to first type
	int TypeIndex = -1;
public:
	TVariant(){
		DefaultConstruction<Types...>();
	};

	~TVariant()
	{
		if (this->SingleTypeData)
		{
			DeleteImpl<Types...>();
		}

		this->SingleTypeData = nullptr;
		this->TypeIndex = -1;
	}


	TVariant(const TVariant& Other)
	{
		SingleTypeData = Other.Copy();
		TypeIndex = Other.TypeIndex;
	}

	TVariant(TVariant&& Other)
	{
		SingleTypeData = Other.SingleTypeData;
		TypeIndex = Other.TypeIndex;

		Other.SingleTypeData = nullptr;
		Other.TypeIndex = -1;
	}

	template<typename DataType>
	requires std::disjunction_v<std::is_same<DataType, Types>...>
	TVariant(const DataType& InValue)
		:SingleTypeData (new DataType(InValue))
		, TypeIndex(TVariantGetIndex<DataType, Types...>::GetIndex())
	{
	}

	template<typename DataType>
	requires std::disjunction_v<std::is_same<DataType, Types>...>
	TVariant& operator=(const DataType& InValue)
	{
		this->Set<DataType>(InValue);
		return *this;
	}

	TVariant& operator=(const TVariant& Other)
	{
		if (this->SingleTypeData)
		{
			DeleteImpl<Types...>();
		}

		this->SingleTypeData = Other.Copy();
		this->TypeIndex = Other.TypeIndex;
		return *this;
	}

	template<typename DataType>
	requires std::disjunction_v<std::is_same<DataType, Types>...>
		TVariant& operator=(DataType&& InValue)
	{
		this->Set<DataType>(std::forward<DataType>(InValue));
		return *this;
	}


	TVariant& operator=(TVariant&& Other)
	{
		if (this->SingleTypeData)
		{
			DeleteImpl<Types...>();
		}

		this->SingleTypeData = Other.SingleTypeData;
		this->TypeIndex = Other.TypeIndex;

		Other.SingleTypeData = nullptr;
		Other.TypeIndex = -1;
		return *this;
	}

	//operator ==
	bool operator==(const TVariant& Other) const
	{
		return this->Compare(Other);

	}

	template<typename DataType>
	void Set(const DataType& InValue)
	{
		//need add this->  to through compile,don't know why
		if (this->SingleTypeData)
		{
			DeleteImpl<Types...>();
		}
		this->SingleTypeData = new DataType(InValue);
		this->TypeIndex = TVariantGetIndex<DataType, Types...>::GetIndex();
	}

	template<typename DataType>
	DataType& Get() 
	{
		assert(this->IsA<DataType>()&& "Data type mismatch");
		return *((DataType*)this->SingleTypeData);
	}

	template<typename DataType>
	const DataType& Get() const
	{
		assert(this->IsA<DataType>() && "Data type mismatch");
		return *((DataType*)this->SingleTypeData);
	}

	template<typename DataType>
	bool IsA() const
	{
		if (this->SingleTypeData)
		{
			return this->TypeIndex == TVariantGetIndex<DataType, Types...>::GetIndex();
		}
		return false;
	}

	template<typename DataType>
	bool IsValidType()
	{
		return false;
	}

	template<typename DataType>
	requires std::disjunction_v<std::is_same<DataType, Types>...>
	bool IsValidType()
	{
		return true;
	}

	// bool HasValue() const
	// {
	// 	return TypeIndex != -1;
	// }

	// void Clear()
	// {
	// 	if (this->SingleTypeData)
	// 	{
	// 		DeleteImpl<Types...>();
	// 	}

	// 	this->SingleTypeData = nullptr;
	// 	this->TypeIndex = -1;
	// }


	//Visit
	template<typename VisitorType>
	void Visit(VisitorType&& Visitor)
	{
		VisitImpl<Types...>(std::forward<VisitorType>(Visitor));
	}

	template<typename VisitorType>
	void Visit(VisitorType&& Visitor) const
	{
		VisitImpl<Types...>(std::forward<VisitorType>(Visitor));
	}


private:
	template<typename... Types2>
	requires (sizeof...(Types2) == 0)
	void DeleteImpl() const
	{	
		assert(false&& "Never invoke this function");
	}

	//delete data pointer
	template<typename FirstType,typename... LeftTypes>
	void DeleteImpl() 
	{
		assert(this->TypeIndex != -1);

		if (this->TypeIndex != TVariantGetIndex<FirstType, Types...>::GetIndex())
		{
			this->DeleteImpl<LeftTypes...>();
		}
		else
		{
			delete (FirstType*)this->SingleTypeData;
		}
	}

	void* Copy() const
	{
		if (!this->SingleTypeData)
		{
			return nullptr;
		}

		return CopyImpl<Types...>();
	}
	

	template<typename FirstType,typename... LeftTypes>
	void* CopyImpl() const
	{
		assert(this->TypeIndex != -1);

		if (this->TypeIndex != TVariantGetIndex<FirstType, Types...>::GetIndex())
		{
			return this->CopyImpl<LeftTypes...>();
		}
		else
		{
			return new FirstType(Get<FirstType>());
		}
	}

	// only for pass compile,should never invoke this function
	template<typename... Types2>
	requires (sizeof...(Types2) == 0)
	void* CopyImpl() const
	{
		assert(false&& "Never invoke this function");
		return nullptr;
	}

	bool Compare(const TVariant& Other) const
	{	
		if(this->TypeIndex != Other.TypeIndex)
		{
			return false;
		}
		// if(this->TypeIndex==-1)
		// {
		// 	return true;
		// }

		return CompareImpl<Types...>(Other.SingleTypeData);
	}

	template<typename FirstType,typename... LeftTypes>
	bool CompareImpl(void* OtherData) const
	{
		assert(this->TypeIndex != -1);
		
		if (this->TypeIndex != TVariantGetIndex<FirstType, Types...>::GetIndex())
		{
			return this->CompareImpl<LeftTypes...>(OtherData);
		}
		else
		{
			return Get<FirstType>() == *((FirstType*)OtherData);
		}
	}

	// only for pass compile,should never invoke this function
	template<typename... Types2>
	requires (sizeof...(Types2) == 0)
	bool CompareImpl(void* OtherData) const
	{
		assert(false&& "Never invoke this function");
		return false;
	}

	template<typename FirstType,typename... LeftTypes>
	void DefaultConstruction()
	{
		this->SingleTypeData=new FirstType();
		this->TypeIndex = TVariantGetIndex<FirstType, Types...>::GetIndex();
	}

	template<typename FirstType,typename... LeftTypes,typename VisitorType>
	void VisitImpl(VisitorType&& Visitor)
	{
		if (this->TypeIndex != TVariantGetIndex<FirstType, Types...>::GetIndex())
		{
			this->VisitImpl<LeftTypes...>(std::forward<VisitorType>(Visitor));
		}
		else
		{
			Visitor(Get<FirstType>());
		}
	}

	template<typename FirstType,typename... LeftTypes,typename VisitorType>
	void VisitImpl(VisitorType&& Visitor) const
	{
		if (this->TypeIndex != TVariantGetIndex<FirstType, Types...>::GetIndex())
		{
			this->VisitImpl<LeftTypes...>(std::forward<VisitorType>(Visitor));
		}
		else
		{
			Visitor(Get<FirstType>());
		}
	}

	//just for pass compile,should never invoke this function
	template<typename VisitorType>
	void VisitImpl(VisitorType&& Visitor) const
	{
		assert(false&& "Never invoke this function");
	}

};

